package com.atlin.project.service.impl;

import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONNull;
import cn.hutool.json.JSONUtil;
import com.atlin.project.common.ErrorCode;
import com.atlin.project.contant.CommonConstant;
import com.atlin.project.exception.BusinessException;
import com.atlin.project.exception.ThrowUtils;
import com.atlin.project.mapper.QuotationDataMapper;
import com.atlin.project.model.domain.QuotationData;
import com.atlin.project.model.domain.QuotationStatistics;
import com.atlin.project.model.domain.User;
import com.atlin.project.model.dto.quotationData.QuotationDataCaptureRequest;
import com.atlin.project.model.dto.quotationData.QuotationDataQueryRequest;
import com.atlin.project.model.dto.quotationData.QuotationDataRecommendQuery;
import com.atlin.project.model.dto.quotationData.QuotationStatisticsQuery;
import com.atlin.project.model.enums.QuotationCaptureTypeEnum;
import com.atlin.project.model.vo.QuotationDataRecommendVO;
import com.atlin.project.model.vo.QuotationDataVO;
import com.atlin.project.service.QuotationDataService;
import com.atlin.project.service.UserService;
import com.atlin.project.utils.SqlUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.xml.stream.events.Characters;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author atlin
 * @description 针对表【quotation_data(语录数据表)】的数据库操作Service实现
 * @createDate 2024-07-11 09:26:50
 */
@Service
@Slf4j
public class QuotationDataServiceImpl extends ServiceImpl<QuotationDataMapper, QuotationData>
        implements QuotationDataService {

    // 语录推荐 Redis key前缀
    private static final String QUOTATIONRECOMMENDKEY = "quotation_recommend:";

    @Resource
    private UserService userService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private QuotationDataMapper quotationDataMapper;


    @Override
    public void validQuotationData(QuotationData quotationData, boolean add) {
        String content = quotationData.getContent();
        String fromAddr = quotationData.getFromAddr();
        String fromWho = quotationData.getFromWho();
        ThrowUtils.throwIf(StringUtils.isAnyBlank(content, fromWho, fromAddr), ErrorCode.PARAMS_ERROR);
    }

    @Override
    public QuotationDataVO getQuotationDataVO(QuotationData quotationData, HttpServletRequest request) {
        return QuotationDataVO.objToVo(quotationData);
    }

    /**
     * 获取查询包装类（用户根据哪些字段查询，根据前端传来的请求对象，得到 mybatis 框架支持的查询 QueryWrapper 类）
     *
     * @param quotationDataQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<QuotationData> getQueryWrapper(QuotationDataQueryRequest quotationDataQueryRequest) {
        QueryWrapper<QuotationData> queryWrapper = new QueryWrapper<>();
        if (quotationDataQueryRequest == null) {
            return queryWrapper;
        }

        Long id = quotationDataQueryRequest.getId();
        String searchText = quotationDataQueryRequest.getSearchText();
        String content = quotationDataQueryRequest.getContent();
        String fromAddr = quotationDataQueryRequest.getFromAddr();
        String fromWho = quotationDataQueryRequest.getFromWho();
        Integer dataLength = quotationDataQueryRequest.getDataLength();
        String sortField = quotationDataQueryRequest.getSortField();
        String sortOrder = quotationDataQueryRequest.getSortOrder();
        Date createTime = quotationDataQueryRequest.getCreateTime();

        queryWrapper.like(StringUtils.isNotBlank(content), "content", content);
        queryWrapper.like(StringUtils.isNotBlank(searchText), "content", searchText);
        queryWrapper.like(StringUtils.isNotBlank(fromAddr), "fromAddr", fromAddr);
        queryWrapper.like(StringUtils.isNotBlank(searchText), "fromAddr", searchText);
        queryWrapper.like(StringUtils.isNotBlank(fromWho), "fromWho", fromWho);
        queryWrapper.like(StringUtils.isNotBlank(searchText), "fromWho", searchText);

        queryWrapper.eq(ObjectUtils.isNotEmpty(dataLength), "dataLength", dataLength);
        queryWrapper.ge(ObjectUtils.isNotEmpty(createTime), "createTime", createTime);

        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq("isDelete", false);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC), sortField);

        return queryWrapper;
    }

    @Override
    public Page<QuotationDataVO> getQuotationDataVOPage(Page<QuotationData> quotationDataPage, HttpServletRequest request) {
        List<QuotationData> quotationDataList = quotationDataPage.getRecords();
        Page<QuotationDataVO> quotationDataVOPage = new Page<>(quotationDataPage.getCurrent(), quotationDataPage.getSize(), quotationDataPage.getTotal());
        if (CollectionUtils.isEmpty(quotationDataList)) {
            return quotationDataVOPage;
        }
        // 填充信息
        List<QuotationDataVO> quotationDataVOList = quotationDataList.stream().map(QuotationDataVO::objToVo).collect(Collectors.toList());
        quotationDataVOPage.setRecords(quotationDataVOList);
        return quotationDataVOPage;
    }

    /**
     * 抓取语录数据
     */
    @Override
    public void captureQuoteData() {
        final long AWAIT_TIME = 5000;
        final String TARGET_PATH = "https://v1.hitokoto.cn/?c=d&c=e&c=h&c=i&c=k";
        int count = 10;

        ArrayList<QuotationData> dataArrayList = new ArrayList<>();
        try {
            long startTime = System.currentTimeMillis();
            while (count-- > 0) {
                String result = HttpRequest.post(TARGET_PATH)
                        .timeout(20000)//超时，毫秒
                        .execute().body();

                Map<String, Object> res = JSONUtil.toBean(result, Map.class);
                String content = res.get("hitokoto") instanceof JSONNull ? "" : (String) res.get("hitokoto");

                if (StringUtils.isNotBlank(content)) {
                    Object fromAddr = res.get("from");
                    Object fromWho = res.get("from_who");
                    QuotationData quotationData = new QuotationData();
                    quotationData.setContent(content);
                    quotationData.setFromAddr(fromAddr instanceof JSONNull ? "网络" : (String) fromAddr);
                    quotationData.setFromWho(fromWho instanceof JSONNull ? "佚名" : (String) fromWho);
                    quotationData.setDataLength(quotationData.getContent().length());
                    dataArrayList.add(quotationData);
                }
                Thread.sleep(AWAIT_TIME);
            }
            boolean result = this.saveBatch(dataArrayList);

            long endTime = System.currentTimeMillis();
            log.info("共抓取数据{}条，入库{}，耗时{}毫秒。", dataArrayList.size(), result ? "成功" : "失败", endTime - startTime);

        } catch (Exception e) {
            log.error("数据抓取失败", e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }

    @Override
    public void captureQuoteData(QuotationDataCaptureRequest quotationDataCaptureRequest) {
        String type = quotationDataCaptureRequest.getType();
        String path = quotationDataCaptureRequest.getPath();
        QuotationCaptureTypeEnum enumByValue = QuotationCaptureTypeEnum.getEnumByValue(type);
        // 1. 参数校验
        if (enumByValue == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "类型错误");
        }
        if (StringUtils.isBlank(path)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 2. 获取语录数据
        List<QuotationData> quotationDataList;
        
        if (QuotationCaptureTypeEnum.WEB.getValue().equals(type)) {
            quotationDataList = getInternetQuotationStr(path);
        } else {
            quotationDataList = getNativeQuotationStr(path);
        }
        
        // 3. 数据入库
        saveBatch(quotationDataList);
    }


    /**
     * 读取本地路径下的语录数据
     *
     * @param filePath 文件路径
     * @return 文本字符串
     */
    private List<QuotationData> getNativeQuotationStr(String filePath) {
        List<QuotationData> res = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (StringUtils.isBlank(line)) continue;
                line = line.replaceAll("[0-9]+、", "、");
                QuotationData quotationData = parseStrToQuotationData(line);
                if (quotationData == null) continue;
                res.add(quotationData);
            }
        } catch (IOException e) {
            log.error("Read Native Quotation Data fail.", e);
        }
        return res;
    }

    /**
     * 读取网络上的语录数据
     *
     * @param url 读取网址
     * @return 文本字符串
     */
    private List<QuotationData> getInternetQuotationStr(String url) {
        if (StringUtils.isBlank(url)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "抓取网址为空。");
        }
        List<QuotationData> res = new ArrayList<>();
        try {
            Document document = Jsoup.connect(url).get();
            Elements elements = document.select("div.content");
            String[] split = elements.text().split("[0-9]+");
            for (String str : split) {
                if (StringUtils.isBlank(str)) continue;
                QuotationData quotationData = parseStrToQuotationData(str);
                if (quotationData == null) continue;
                res.add(quotationData);
            }

        } catch (IOException e) {
            log.error("Parse ${} fail.", url, e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "解析失败。");
        }
        return res;
    }

    /**
     * 将字符串语录数据转化为实体对象
     *
     * @param str
     * @return
     */
    private QuotationData parseStrToQuotationData(String str) {
        if (StringUtils.isBlank(str)) {
            return null;
        }
        List<String> result = Arrays.stream(str.split("、|。|——|－|--")).filter(s -> !StringUtils.isBlank(s)).collect(Collectors.toList());
        if (result.size() >= 2) {
            String content;
            String fromWho;
            if (result.size() == 3) {
                content = (result.get(0) + result.get(1)).trim();
                fromWho = result.get(2).trim().replaceAll("——|－|--", "");
            } else {
                content = result.get(0).trim();
                fromWho = result.get(1).trim().replaceAll("——|－|--", "");
            }
            QuotationData quotationData = new QuotationData();
            quotationData.setContent(content);
            quotationData.setFromAddr("三重门");
            quotationData.setFromWho(fromWho);
            quotationData.setDataLength(content.length());
            return quotationData;
        }
        return null;
    }


    @Override
    public QuotationDataRecommendVO quotationRecommend(QuotationDataRecommendQuery quotationDataRecommendQuery, HttpServletRequest request) {
        if (quotationDataRecommendQuery == null || quotationDataRecommendQuery.getResetRecommend() == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        int resetRecommend = quotationDataRecommendQuery.getResetRecommend();
        User loginUser = userService.getLoginUser(request);
        Long userId = loginUser.getId();

        // 查询是否已有缓存
        String res = redisTemplate.opsForValue().get(getRedisKey(userId));
        // 数据为空 设置缓存后直接返回
        if (StringUtils.isBlank(res)) {
            return setRecommendCache(userId);
        }
        // 数据存在 不需要重置推荐
        if (resetRecommend == 0) {
            return JSONUtil.toBean(res, QuotationDataRecommendVO.class);
        }
        // 数据存在 需要重新推荐
        if (resetRecommend == 1) {
            return setRecommendCache(userId);
        }

        return null;
    }

    @Override
    public QuotationStatistics getQuotationStatistics(QuotationStatisticsQuery quotationDataRecommendQuery) {
        int fromAddrQueryNum = quotationDataRecommendQuery.getFromAddrQueryNum();
        int fromWhoQueryNum = quotationDataRecommendQuery.getFromWhoQueryNum();

        long total = this.count();
        long todayTotal = quotationDataMapper.selectTodayTotal();
        List<QuotationStatistics.FromAddrStatistics> topFromAddr = quotationDataMapper.selectTopFromAddr(fromAddrQueryNum);
        List<QuotationStatistics.FromWhoStatistics> topFromWho = quotationDataMapper.selectTopFromWho(fromWhoQueryNum);

        QuotationStatistics result = new QuotationStatistics();
        result.setTotal(total);
        result.setTodayTotal(todayTotal);
        result.setFromAddrTop(topFromAddr);
        result.setFromWhoTop(topFromWho);

        return result;
    }

    @Override
    public List<QuotationDataVO> getFromAddrDetail(List<String> fromAddrList) {
        QueryWrapper<QuotationData> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("fromAddr", fromAddrList);
        queryWrapper.last("limit 100");
        List<QuotationData> dataList = this.list(queryWrapper);
        return dataList.stream().map(QuotationDataVO::objToVo).collect(Collectors.toList());
    }

    @Override
    public List<QuotationDataVO> getFromWhoDetail(List<String> fromWhoList) {
        QueryWrapper<QuotationData> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("fromWho", fromWhoList);
        //限制最大条数
        queryWrapper.last("limit 100");
        List<QuotationData> dataList = this.list(queryWrapper);
        return dataList.stream().map(QuotationDataVO::objToVo).collect(Collectors.toList());
    }

    private QuotationDataRecommendVO setRecommendCache(long userId) {
        QuotationDataRecommendVO quotationDataRecommendVO = calculateDailyQuotation();
        redisTemplate.opsForValue().set(getRedisKey(userId), JSONUtil.toJsonStr(quotationDataRecommendVO), 12, TimeUnit.HOURS);
        return quotationDataRecommendVO;
    }

    private QuotationDataRecommendVO calculateDailyQuotation() {
        // 从数据库中获取20条语录
        List<QuotationData> quotationList = getQuotesFromDatabase();
        // 在获取的一页中随机选择一条作为此用户的今日推荐数据
        return getRandomQuote(quotationList);
    }

    private List<QuotationData> getQuotesFromDatabase() {
        // 从数据库中获取语录
        long count = this.count();
        int size = 20;
//        int maxPage = count > size ? (int) Math.ceil(count / (size + 0.0)) : 1;
        int maxPage = (int) Math.ceil((double) (count + size - 1) / size);
        long current = new Random().nextInt(maxPage) + 1;

        Page<QuotationData> quotationDataPage = this.page(new Page<>(current, size), null);
        return quotationDataPage.getRecords();
    }

    private QuotationDataRecommendVO getRandomQuote(List<QuotationData> quotationList) {
        // 生成一个随机数
        int randomIndex = new Random().nextInt(quotationList.size());
        // 返回随机语录

        QuotationData quotationData = quotationList.get(randomIndex);
        QuotationDataRecommendVO quotationDataRecommendVO = new QuotationDataRecommendVO();
        BeanUtils.copyProperties(quotationData, quotationDataRecommendVO);
        return quotationDataRecommendVO;
    }

    private String getRedisKey(Long userId) {
        return QUOTATIONRECOMMENDKEY + userId;
    }

}




